/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-10
 * V4.0
 */
package com.jphenix.clazz;

import com.jphenix.standard.docs.ClassInfo;

import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


/**
 * 方法信息类
 * 
 * 2018-09-13 增加了返回值对象名
 * 2018-09-18 修改了获取参数类型名方法中，没有参数时报错的bug
 * 
 * javassist
 * @author mabg
 */
@ClassInfo({"2018-09-18 17:41","方法信息类"})
public class MethodInfo {
    
    ConstPool constPool;
    int accessFlags;
    int name;
    String cachedName;
    int descriptor;
    ArrayList<AttributeInfo> attribute; // may be null

    /**
     * If this value is true, Javassist maintains a <code>StackMap</code> attribute
     * generated by the <code>preverify</code> tool of J2ME (CLDC).  The initial
     * value of this field is <code>false</code>. 
     */
    public static boolean doPreverify = false;

    /**
     * The name of constructors: <code>&lt;init&gt</code>.
     */
    public static final String nameInit = "<init>";

    /**
     * The name of class initializer (static initializer):
     * <code>&lt;clinit&gt</code>.
     */
    public static final String nameClinit = "<clinit>";

    private MethodInfo(ConstPool cp) {
        constPool = cp;
        attribute = null;
    }

    /**
     * Constructs a <code>method_info</code> structure. The initial value of
     * <code>access_flags</code> is zero.
     * 
     * @param cp
     *            a constant pool table
     * @param methodname
     *            method name
     * @param desc
     *            method descriptor
     * @see Descriptor
     */
    public MethodInfo(ConstPool cp, String methodname, String desc) {
        this(cp);
        accessFlags = 0;
        name = cp.addUtf8Info(methodname);
        cachedName = methodname;
        descriptor = constPool.addUtf8Info(desc);
    }

    MethodInfo(ConstPool cp, DataInputStream in) throws IOException {
        this(cp);
        read(in);
    }


    /**
     * Returns a string representation of the object.
     */
    @Override
    public String toString() {
        return getName() + " " + getDescriptor();
    }



    /**
     * Returns a method name.
     */
    public String getName() {
       if (cachedName == null) {
           cachedName = constPool.getUtf8Info(name);
       }

       return cachedName;
    }


    /**
     * Returns true if this is not a constructor or a class initializer (static
     * initializer).
     */
    public boolean isMethod() {
        String n = getName();
        return !n.equals(nameInit) && !n.equals(nameClinit);
    }

    /**
     * Returns a constant pool table used by this method.
     */
    public ConstPool getConstPool() {
        return constPool;
    }

    /**
     * Returns true if this is a constructor.
     */
    public boolean isConstructor() {
        return getName().equals(nameInit);
    }

    /**
     * Returns true if this is a class initializer (static initializer).
     */
    public boolean isStaticInitializer() {
        return getName().equals(nameClinit);
    }

    /**
     * Returns access flags.
     * 
     * @see AccessFlag
     */
    public int getAccessFlags() {
        return accessFlags;
    }


    /**
     * Returns a method descriptor.
     * 
     * @see Descriptor
     */
    public String getDescriptor() {
        return constPool.getUtf8Info(descriptor);
    }

    /**
     * 获取方法返回值类型名称字符串
     * @return 返回值类型名称字符串
     * 2018年9月13日
     * @author MBG
     */
    public String getReturnTypeName() {
    	String desc = getDescriptor(); //获取方法声明信息
    	if(desc==null || desc.length()<1) {
    		return "";
    	}
    	//返回信息分隔符
    	int point = desc.indexOf(")");
    	if(point<1) {
    		return "";
    	}
    	desc = desc.substring(point+1);
    	if("V".equals(desc)) {
    		return "void";
    	}
    	return desc.replace('/','.');
    }
    

    /**
     * Returns all the attributes.  The returned <code>List</code> object
     * is shared with this object.  If you add a new attribute to the list,
     * the attribute is also added to the method represented by this
     * object.  If you remove an attribute from the list, it is also removed
     * from the method.
     * 
     * @return a list of <code>AttributeInfo</code> objects.
     * @see AttributeInfo
     */
    public List<AttributeInfo> getAttributes() {
        if (attribute == null) {
            attribute = new ArrayList<AttributeInfo>();
        }

        return attribute;
    }

    /**
     * Returns the attribute with the specified name. If it is not found, this
     * method returns null.
     * 
     * @param name attribute name
     * @return an <code>AttributeInfo</code> object or null.
     * @see #getAttributes()
     */
    public AttributeInfo getAttribute(String name) {
        return AttributeInfo.lookup(attribute, name);
    }

    /**
     * Returns an Exceptions attribute.
     * 
     * @return an Exceptions attribute or null if it is not specified.
     */
    public ExceptionsAttribute getExceptionsAttribute() {
        AttributeInfo info = AttributeInfo.lookup(attribute,
                ExceptionsAttribute.tag);
        return (ExceptionsAttribute)info;
    }

    /**
     * Returns a Code attribute.
     * 
     * @return a Code attribute or null if it is not specified.
     */
    public CodeAttribute getCodeAttribute() {
        AttributeInfo info = AttributeInfo.lookup(attribute, CodeAttribute.tag);
        return (CodeAttribute)info;
    }


    /**
     * Returns the line number of the source line corresponding to the specified
     * bytecode contained in this method.
     * 
     * @param pos
     *            the position of the bytecode (&gt;= 0). an index into the code
     *            array.
     * @return -1 if this information is not available.
     */
    public int getLineNumber(int pos) {
        CodeAttribute ca = getCodeAttribute();
        if (ca == null) {
            return -1;
        }

        LineNumberAttribute ainfo = (LineNumberAttribute)ca
                .getAttribute(LineNumberAttribute.tag);
        if (ainfo == null) {
            return -1;
        }

        return ainfo.toLineNumber(pos);
    }


    private void read(DataInputStream in) throws IOException {
        accessFlags = in.readUnsignedShort();
        name = in.readUnsignedShort();
        descriptor = in.readUnsignedShort();
        int n = in.readUnsignedShort();
        attribute = new ArrayList<AttributeInfo>();
        for (int i = 0; i < n; ++i) {
            attribute.add(AttributeInfo.read(constPool, in));
        }
    }
    
    /**
     * 获取传入参数数量
     * @return 传入参数数量
     * 2014年6月10日
     * @author 马宝刚
     */
    public int getParameterCount() {
        return Descriptor.numOfParameters(getDescriptor());
    }
    
    
    /**
     * 获取传入参数变量名数组
     * @return 传入参数变量名数组
     * 2014年6月10日
     * @author 马宝刚
     */
    public String[] getParameterNames() {
        //构建返回值
        String[] res = new String[getParameterCount()];
        if(res.length<1) {
        	return res;
        }
        CodeAttribute attribute = getCodeAttribute();
        if(attribute==null) {
        	//明明存在这个方法，也有传入参数，可是获取该对象为空，导致无法继续获取参数名
        	for(int i=0;i<res.length;i++) {
        		res[i] = "arg"+i;
        	}
        	return res;
        }
        //获取变量属性信息
        LocalVariableAttribute  attr = 
                (LocalVariableAttribute )attribute.getAttribute(LocalVariableAttribute.tag);
        
        //获取变量名称起始位置
        int staticIndex = Modifier.isStatic(getAccessFlags()) ? 0 : 1;  
        for (int i=0;i< res.length;i++)  {
        	try {
              res[i] = attr.variableName(i+staticIndex);  
        	}catch(Exception e) {
        		res[i] = "arg"+i;
        	}
        }
        return res;
    }
}
